package com.lancoo.cvas.arrangecourse.coursescheduling.campusscheduling;

import cn.hutool.core.collection.CollUtil;
import com.lancoo.cvas.arrangecourse.coursescheduling.allocatetimeslotroom.AllocateNoClashTimeslotRoom;
import com.lancoo.cvas.arrangecourse.coursescheduling.allocatetimeslotroom.AllocateTimeslotRoom;
import com.lancoo.cvas.arrangecourse.coursescheduling.assignlist.NoClashAssign;
import com.lancoo.cvas.arrangecourse.coursescheduling.assignlist.NormalAssign;
import com.lancoo.cvas.arrangecourse.entities.ClassHour;
import com.lancoo.cvas.arrangecourse.entities.TeachingClassUnit;
import com.lancoo.cvas.arrangecourse.entities.TimeslotRoom;
import com.lancoo.cvas.arrangecourse.rulecheck.RuleInit;
import com.lancoo.cvas.entity.ArrangeProcess;
import com.lancoo.cvas.pojo.dto.ArrangeCourseRuleDto;
import com.lancoo.cvas.pojo.dto.ArrangeTeacherRuleDto;
import com.lancoo.cvas.pojo.dto.ArrangeTeachingClassDto;
import com.lancoo.cvas.pojo.dto.ArrangeTeachingClassRuleDto;
import com.lancoo.cvas.service.ArrangeProcessService;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * @Description 手动指定排课
 * @Author Fortysun
 * @Date 2023/4/25 10:50
 **/
@Slf4j
public class CampusScheduling {
    //排课规则初始化对象
    RuleInit ruleInit;
    //声明无冲突排课对象
    private NormalAssign noClashScheduing;

    /**
     * 构造函数，初始化按类型，处理教室课时分组列的对象，同时传递排课规则初始化对象
     *
     * @param ruleInit 排课规则初始化类
     */
    public CampusScheduling(RuleInit ruleInit) {
        this.ruleInit = ruleInit;
        //初始化，按教室类型，处理教室课时分组信息列表的对象
        //初始化排课的对象
        if (noClashScheduing == null) {
            //初始化排课使用的教室课时分配的类，分配无冲突的教室课时分组信息；
            AllocateTimeslotRoom allocateNoClashTimeslotRoom = new AllocateNoClashTimeslotRoom();
            //初始化拆班排课，需要满足的规则
            noClashScheduing = new NoClashAssign(ruleInit.getNoClashBaseRule(), allocateNoClashTimeslotRoom);
        }
    }


    /**
     * 开始校区无冲突排课
     *
     * @param taskId                排课选课计划id
     * @param campusId              校区id
     * @param teachingClassList     排课教学班
     * @param timeslotRoomList      教室课时分组
     * @param classHourMap          已排的正常课程，转换过来的排课单元map对象，key为timeslotCode
     * @param teacherRuleList       教师规则列表
     * @param courseRuleList        课程排课规则信息列表
     * @param leaveList             剩余的排课教学班排课单元列表
     * @param teachingClassRuleList 固排规则
     * @return
     */
    public LinkedList<TeachingClassUnit> start(Integer taskId,
                                               String campusId,
                                               List<ArrangeTeachingClassDto> teachingClassList,
                                               LinkedList<TimeslotRoom> timeslotRoomList,
                                               ConcurrentHashMap<String, CopyOnWriteArrayList<ClassHour>> classHourMap,
                                               List<ArrangeTeacherRuleDto> teacherRuleList,
                                               List<ArrangeCourseRuleDto> courseRuleList,
                                               LinkedList<TeachingClassUnit> leaveList,
                                               List<ArrangeTeachingClassRuleDto> teachingClassRuleList,
                                               ArrangeProcessService arrangeProcessService) {

        int normal = 0;

        //拆班排课，鮀滨职校需求
        leaveList = splitScheduling(taskId, campusId, ruleInit, teachingClassList,
                timeslotRoomList, classHourMap, leaveList);
        //优先排排课指定教室
        leaveList = fixScheduling(taskId, campusId, ruleInit, teachingClassList,
                timeslotRoomList, classHourMap, teacherRuleList, courseRuleList, leaveList, teachingClassRuleList,true);
        //指定教室排课
        leaveList = fixRoomScheduling(taskId, campusId, ruleInit, teachingClassList,
                timeslotRoomList, classHourMap, leaveList);
        //优先排排课没有教室
        leaveList = fixScheduling(taskId, campusId, ruleInit, teachingClassList,
                timeslotRoomList, classHourMap, teacherRuleList, courseRuleList, leaveList, teachingClassRuleList,false);

        log.info("#CampusScheduling.start#--校区：" + campusId
                + "开始进行第一轮无冲突排课，并且启用基础规则过滤，"
                + "教学班数量：" + (teachingClassList != null ? teachingClassList.size() : 0)
                + "剩余排课教学班单元数量：" + (leaveList != null ? leaveList.size() : 0)
                + "可用教室课时分组数量：" + (timeslotRoomList != null ? timeslotRoomList.size() : 0));

        //开启第一轮无冲突排课，启用基础规则做过滤，同时会进行跨校区规则、冲突、禁排规则的检测
        leaveList = noClashScheduing.start(taskId, teachingClassList,
                leaveList, timeslotRoomList, classHourMap,new ArrayList<>());

        log.info("#CampusScheduling.start#--校区：" + campusId
                + "第一轮无冲突排课结束，"
                + "剩余教学班排课单元数量：" + (leaveList != null ? leaveList.size() : 0)
                + "，可用教室课时分组数量：" + (timeslotRoomList != null ? timeslotRoomList.size() : 0));

        for (String key : classHourMap.keySet()) {
            if (!key.equals("NoTimeslotRoom")
                    && !key.equals("ClashesTimeslotRoom")) {
                normal += classHourMap.get(key).size();
            }
        }
        int unSuitBasePre = 0;
        if (CollUtil.isNotEmpty(leaveList)){
            unSuitBasePre = leaveList.stream().mapToInt(TeachingClassUnit::getConnectSection).sum();
        }
        //保存排课基础规则满足率
        ArrangeProcess arrangeProcess = arrangeProcessService.lambdaQuery().eq(ArrangeProcess::getTaskId, taskId).one();
        Long id = null;
        if (arrangeProcess != null) {
            id = arrangeProcess.getId();
        }
        arrangeProcessService.saveOrUpdate(new ArrangeProcess().setId(id).setTaskId(Long.valueOf(taskId))
                .setAllTeachingClass(normal + unSuitBasePre).setNormal(normal).setUnsuitBase(unSuitBasePre));


        //第二轮排课
        if (leaveList != null && !leaveList.isEmpty()) {
            log.info("#CampusScheduling.start#----校区：" + campusId
                    + "开始进行第二轮无冲突排课，不启用基础规则过滤");

            //第二轮选课，重新设置规则检测服务
            noClashScheduing.setRuleService(ruleInit.getNoClashRule());
            //开启第二轮无冲突排课，不检测基础规则
            leaveList = noClashScheduing.start(taskId, null, leaveList, timeslotRoomList, classHourMap,new ArrayList<>());

            log.info("#CampusScheduling.start#----校区：" + campusId
                    + "第二轮无冲突排课结束，"
                    + "剩余教学班排课单元数量：" + (leaveList != null ? leaveList.size() : 0)
                    + "，可用教室课时分组数量：" + (timeslotRoomList != null ? timeslotRoomList.size() : 0));

            int unSuitBasePost = 0;
            if (CollUtil.isNotEmpty(leaveList)){
                unSuitBasePost = leaveList.stream().mapToInt(TeachingClassUnit::getConnectSection).sum();
            }


            arrangeProcessService.saveOrUpdate(new ArrangeProcess().setId(id).setTaskId(Long.valueOf(taskId))
                    .setAllTeachingClass(normal + unSuitBasePre).setNormal(normal).setUnsuitBase(unSuitBasePre-unSuitBasePost));

        }

        return leaveList;
    }

    /**
     * 拆班排课，鮀滨职校需求
     *
     * @param taskId            排课选课计划id
     * @param campusId          校区id
     * @param ruleInit          初始化排课规则
     * @param teachingClassList 所有待排课的教学班信息列表
     * @param timeslotRoomList  所有待分配的教室课时分组信息列表
     * @param classHourMap      所有已分配的课表信息
     * @param leaveList         剩余未分配的排课单元信息
     * @return
     */
    private LinkedList<TeachingClassUnit> splitScheduling(Integer taskId,
                                                          String campusId,
                                                          RuleInit ruleInit,
                                                          List<ArrangeTeachingClassDto> teachingClassList,
                                                          LinkedList<TimeslotRoom> timeslotRoomList,
                                                          ConcurrentHashMap<String, CopyOnWriteArrayList<ClassHour>> classHourMap,
                                                          LinkedList<TeachingClassUnit> leaveList) {
        log.info("#CampusScheduling.splitScheduling#--校区：" + campusId
                + "开始进行拆班排课");

        //需要拆班的教学班的排课处理，去指定教学班的教室课时分组信息
        LinkedList<TeachingClassUnit> splicClassList = new SplitTeachingClassScheduling(ruleInit)
                .start(taskId, teachingClassList,
                        timeslotRoomList, classHourMap);

        log.info("#CampusScheduling.SplitScheduling#--校区：" + campusId
                + "拆班排课完毕，剩余未排教学班排课单元数量：" + (splicClassList != null ? splicClassList.size() : 0));

        //如果剩余的排课教学班列表不为空
        if (splicClassList != null && !splicClassList.isEmpty()) {
            //加入进行处理
            leaveList.addAll(splicClassList);
        }

        return leaveList;
    }

    /**
     * 拆班排课，鮀滨职校需求
     *
     * @param taskId                排课选课计划id
     * @param campusId              校区id
     * @param ruleInit              初始化排课规则
     * @param teachingClassList     所有待排课的教学班信息列表
     * @param timeslotRoomList      所有待分配的教室课时分组信息列表
     * @param classHourMap          所有已分配的课表信息
     * @param teacherRuleList       教师规则列表
     * @param courseRuleList        课程排课规则信息列表
     * @param leaveList             剩余未分配的排课单元信息
     * @param teachingClassRuleList 固排规则
     * @return
     */
    private LinkedList<TeachingClassUnit> fixScheduling(Integer taskId,
                                                        String campusId,
                                                        RuleInit ruleInit,
                                                        List<ArrangeTeachingClassDto> teachingClassList,
                                                        LinkedList<TimeslotRoom> timeslotRoomList,
                                                        ConcurrentHashMap<String, CopyOnWriteArrayList<ClassHour>> classHourMap,
                                                        List<ArrangeTeacherRuleDto> teacherRuleList,
                                                        List<ArrangeCourseRuleDto> courseRuleList,
                                                        LinkedList<TeachingClassUnit> leaveList,
                                                        List<ArrangeTeachingClassRuleDto> teachingClassRuleList,
                                                        boolean withRoom) {
        log.info("#CampusScheduling.fixScheduling#--校区：" + campusId
                + "开始进行优先排排课");
        //尽量排规则的处理，其实就是根据尽量排规则，去指定教学班的教室课时分组信息
        LinkedList<TeachingClassUnit> fixClassList = new FixScheduling(ruleInit).start(taskId,
                teachingClassList, teacherRuleList, courseRuleList, timeslotRoomList, classHourMap, teachingClassRuleList,withRoom);

        log.info("#CampusScheduling.fixScheduling#--校区：" + campusId
                + "优先排排课完毕，剩余未排教学班排课单元数量：" + (fixClassList != null ? fixClassList.size() : 0));

        //如果剩余的排课教学班列表不为空
        if (fixClassList != null && !fixClassList.isEmpty()) {
            //加入进行处理
            leaveList.addAll(fixClassList);
        }

        return leaveList;
    }

    /**
     * 拆班排课，鮀滨职校需求
     *
     * @param taskId            排课选课计划id
     * @param campusId          校区id
     * @param ruleInit          初始化排课规则
     * @param teachingClassList 所有待排课的教学班信息列表
     * @param timeslotRoomList  所有待分配的教室课时分组信息列表
     * @param classHourMap      所有已分配的课表信息
     * @param leaveList         剩余未分配的排课单元信息
     * @return
     */
    private LinkedList<TeachingClassUnit> fixRoomScheduling(Integer taskId,
                                                            String campusId,
                                                            RuleInit ruleInit,
                                                            List<ArrangeTeachingClassDto> teachingClassList,
                                                            LinkedList<TimeslotRoom> timeslotRoomList,
                                                            ConcurrentHashMap<String, CopyOnWriteArrayList<ClassHour>> classHourMap,
                                                            LinkedList<TeachingClassUnit> leaveList) {
        log.info("#CampusScheduling.fixRoomScheduling#--校区：" + campusId
                + "开始进行指定教室排课");
        //尽量排规则的处理，其实就是根据尽量排规则，去指定教学班的教室课时分组信息
        LinkedList<TeachingClassUnit> fixRoomList = new FixRoomScheduling(ruleInit).start(taskId, teachingClassList,
                timeslotRoomList, classHourMap);

        log.info("#CampusScheduling.fixRoomScheduling#--校区：" + campusId
                + "指定教室排课完毕，剩余未排教学班排课单元数量：" + (fixRoomList != null ? fixRoomList.size() : 0));

        //如果剩余的排课教学班列表不为空
        if (fixRoomList != null && !fixRoomList.isEmpty()) {
            //加入进行处理
            leaveList.addAll(fixRoomList);
        }

        return leaveList;
    }


}
